#include <iostream>
#include <map>
#include <vector>
#include <fstream>
#include <string>
#include <cstdio>
#include <cstdlib>

using namespace std;

#include "IPE.h"

IPE::IPE (vector<string> *i)
{
	instrs = i;
}

IPE::~IPE ()
{
	instrs = NULL;
}

int IPE::ParseAssembly (char* szFileName, int& mainIndex)
{
	const int MAXSIZE = 2048;
	int lineCount = 0;
	char buf [MAXSIZE];
	string readString, tempString, finalString, parseString;

	mainIndex = -1;

	ifstream in (szFileName);
	if (!in) errprintline ("Cannot open input file", lineCount);

	while (!in.eof()) {
		in.getline (buf, MAXSIZE-1);
		readString = buf;

		// Find the start of the comment and remove comment
		int commentIndex = readString.find ("!");
		if (commentIndex != string::npos) {
			tempString.assign (readString, 0, commentIndex);
		} else {
			tempString = readString;
		}
		
		parseString = ParseString (tempString);
		if (parseString != "") {
	
			// Check if it is main label.if yes, then make mainIndex
			// point to this line
			if (parseString == "main:" && mainIndex == -1)
				mainIndex = lineCount+1;
			else if (parseString == "main:")
				errprintline ("main redeclared", lineCount+1);
	
			finalString = ValidateString (parseString, lineCount+1);
			if (finalString.size() != 0) {
				instrs->push_back (finalString);
				lineCount++;
			}
		}
	}

	RecheckLabels ();
	return lineCount;
}

/*
 * Parses string and removes any white space at the start and
 * end of the string
 */
string IPE::ParseString (string instr)
{
	string finalString;
	int length = instr.size(), i = 0, 
	    start = 0, end = 0;
	bool stflag = false, endflag = false;
	while ( i < length) {
		if (!stflag && ( !isspace (instr[i]) ) ) {
			start = i;
			stflag = true;
			
		}
		if (!endflag && (!isspace (instr[length-i])) && 
				instr[length-i] != '\n' ) {
			end = length-i;
			endflag = true;
		}
		i++;
	}
	if (start < end)
	finalString.assign (instr, start, end);
	else
	finalString = "";
	return finalString;
}

/*
 * Validates string by comparing it with valid instruction
 * syntax
 */
string IPE::ValidateString (string str, int lineCount)
{
	string instr, param, finalString;
	char *cparam, *cstr, *cinstr, *ctest;
	int temp, args;
	
	cstr = (char*)str.c_str ();
	cinstr = (char*)malloc (str.size() + 1);
	cparam = (char*)malloc (str.size() + 1);
	ctest = (char*) malloc (str.size() + 1);
	if ((args = sscanf (cstr, "%s%s%s", cinstr, cparam, ctest)) == 3) 
		errprintline ("Illegal instruction", lineCount);

	instr = cinstr;
	int len = instr.size();
	
	if (instr == "lda" || instr == "sta" || instr == "add" || 
			instr == "sub" || instr == "and" || instr == "or") {
		// It is a normal instruction, check the second parameter
	
		if (args != 2)
			errprintline ("Invalid operand ", lineCount);
		
		if (cparam[0] == '#')
			sscanf (cparam,"#$%d", &temp);
		else if (cparam[0] == '$')
			sscanf (cparam,"$%d", &temp);
		else if (sscanf(cparam,"%d", &temp) == 0)
			errprintline("Invalid operand ",lineCount);
		param = cparam;

		finalString = instr + " " + param;
		return finalString;
	} 
	else if (instr == "nota") {
		if (args != 1)
			errprintline ("Invalid operand ", lineCount);
		finalString = instr;
		return finalString;
	}
	else if (instr == "ble" || instr == "bge" || instr == "bl" ||
		instr == "bg" || instr == "brc" || instr == "ba" || 
		instr == "be") {
		// It is a branch instruction, modify it to point to index
	
		if (args != 2) errprintline ("Invalid operand ", lineCount);	
	       	
		param = cparam;
		
		map<string, int>::iterator iter = LabelMap.find (param);
		if (iter == LabelMap.end()) {
			// label does not exist, add to unknown labels list
			LabelList.insert (LabelList.end(), 
					make_pair (param, lineCount));
			finalString = instr;
		} else {
			// label is present, replace label name with index
			char buf[32];
			sprintf (buf,"%d", iter->second);
			finalString = instr + " " + buf;
		}
		return finalString;
	} 
	else if (instr == "nop")
		return instr;
	else if (instr[len-1] == ':') { 
		// It is a label, add it into a list
		if (args != 1) errprintline ("Invalid declaration ", lineCount);
		
		cinstr[len-1] = '\0';
		map<string, int>::iterator iter = LabelMap.find (cinstr);
		if (iter == LabelMap.end()) {
			// label does not exist, add it
			LabelMap.insert (make_pair(cinstr, lineCount));
			// use nop instead of label name
			finalString = "nop";
		} else {
			// label already exists, error
			errprintline("Label already exists", 0);
		}
	}
	return finalString;
}

void IPE::RecheckLabels ()
{
	multimap<string,int>::iterator iter;
	map<string,int>::iterator fnditer;
	
	iter = LabelList.begin ();
	while (iter != LabelList.end()) {
		fprintf (stderr,"[%s]  ", iter->first.c_str());
		if((fnditer = LabelMap.find (iter->first)) != LabelMap.end())
		{
			// Yes, the label is found, so lets modify that 
			// particular instruction to point to the right
			// line
			string sbuf, str = (*instrs)[iter->second];
			char buf[8];
			sprintf (buf, "%d", fnditer->second);
			(*instrs)[iter->second] =  str+" "+buf;
		} else {
			// Label not found, we have to print error
			errprintline ("Label not declared anywhere", -1);
		}
		iter++;
	}
}

void IPE::ParseInstruction (string str, string& inst, string& param)
{
	const char *cstr;
	char *cinst, *cparam;
	cstr = str.c_str ();
	cinst = (char*)malloc (str.size()+1);
	cparam = (char*)malloc (str.size()+1);
	sscanf(cstr,"%s%s", cinst, cparam);
	inst = cinst;
	param = cparam;
}

void IPE::errprintline (char *msg, int line)
{
	cerr << msg << endl;
}

